# encoding: utf-8
import datetime
import traceback

import requests

from app.extensions import log, db
from flask_restplus import Resource
from app.extensions.api import Namespace
from app.modules.common.mailUtil import sendMail
from app.modules.common.utils import DbCommit, checkToken, recursive_json_loads
from app.modules.common.schemas import BaseSchema
from . import parameters, schemas
from .models import ApplyRecord
from app.modules.operation.resources import addOperationRecord
from config import BaseConfig
from flask_restplus_patched import Parameters
from app.modules.agent.models import Agent

api = Namespace('apply_record', description = "申请记录")  # pylint: disable=invalid-name


@api.route('/')
class ApplyRecordList(Resource):
    @api.parameters(parameters.ApplyRecordListParameters())
    @api.response(schemas.ApplyRecordListSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        查询申请记录列表
        :param args:
        :return:
        """
        log.info("============== start query apply-record list ==============")
        _record_id = args['record_id']
        _plan_name = args['plan_name']
        _plan_id = args['plan_id']
        _isOnlyoverdue = args['isOnlyoverdue']
        _isOnlyIntegration = args['isOnlyIntegration']
        _isOnlyOneMouth = args['isOnlyOneMouth']

        _query = ApplyRecord.query
        if _record_id is not None:
            _query = _query.filter_by(record_id = _record_id)
        if _plan_id is not None:
            _query = _query.filter_by(plan_id = _plan_id)
        if _plan_name is not None:
            _query = _query.filter_by(plan_name = _plan_name)
        if _isOnlyIntegration is not None and _isOnlyIntegration:
            _query = _query.filter_by(isIntegration = '0')
        if _isOnlyOneMouth:
            now = datetime.datetime.now() - datetime.timedelta(days = 30)
            _query = _query.filter(ApplyRecord.create_date > now)

        _list = _query.order_by(ApplyRecord.create_date.desc()).all()

        if _isOnlyoverdue is not None and _isOnlyoverdue:
            _l = []
            # 只显示超期申请
            _now = datetime.date.today()
            for _row in _list:
                if _row.agent_ip is not None and _now > _row.expect_recover_date:
                    _l.append(_row)
            return {'success': True, 'msg': '', 'data': _l}
        return {'success': True, 'msg': '', 'data': _list}


@api.route('/audit')
class RecordAudit(Resource):
    @api.parameters(parameters.RecordAuditParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        审核申请记录
        :param args:
        :return:
        """
        log.info("============== start audit record ==============")
        _record_id = args['record_id']
        _audit_id = args['audit_id']
        _audit_name = args['audit_name']
        _level_name = args['level_name']
        _added_desc = args['added_desc']
        log.info('============== record_id is %s ==============' % _record_id)
        log.info('============== audit_id is %s ==============' % _audit_id)
        log.info('============== audit_name is %s ==============' % _audit_name)
        log.info('============== level_name is %s ==============' % _level_name)
        log.info('============== added_desc is %s ==============' % _added_desc)
        _record = ApplyRecord.query.filter_by(record_id = _record_id).first()
        if _record is None:
            _msg = '申请记录不存在 record_id:%s' % _record_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        else:
            addOperationRecord('AUDIT_APPLY', _record_id, None, None, 'audit record', **kwargs)

            _record.audit_id = _audit_id
            _record.audit_name = _audit_name
            _record.apply_load_level_name = _level_name
            _record.added_desc = _added_desc
            _record.audit_time = datetime.datetime.now()
            _record.status = '审批通过,等待分配'

            # 发送邮件通知
            sendMail(_to = 'lunan.xu@winit.com.cn,tongsan.chen@winit.com.cn,min.wan@winit.com.cn',
                     _subject = 'ucloud申请审批通过',
                     _body = '项目[%s]ucloud[%s]环境审批通过' % (_record.plan_name, _record.type))

            return DbCommit()


@api.route('/assign')
class RecordAudit(Resource):
    @api.parameters(parameters.RecordAssignParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        为申请分配agent
        :param args:
        :return:
        """
        log.info("============== start assign agent for record ==============")
        _record_id = args['record_id']
        _agent_ip = args['agent_ip']
        log.info("============== record_id is %s ==============" % _record_id)
        log.info("============== agent_ip is %s ==============" % _agent_ip)
        _record = ApplyRecord.query.filter_by(record_id = _record_id).first()
        if _record is None:
            _msg = '申请记录不存在 record_id:%s' % _record_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        # elif _record.agent_ip is not None:
        #     _msg = '该申请记录已分配agent record_id:%s' % _record_id
        #     log.error('============== %s ==============' % _msg)
        #     return {'success': False, 'msg': _msg}
        else:
            _count = ApplyRecord.query.filter_by(agent_ip = _agent_ip).first()
            if _count is not None:
                _count.status = 'agent被回收'
                _count.agent_ip = None
                _count.recover_time = datetime.datetime.now()
            addOperationRecord('ASSIGN_APPLY', _record_id, None, _agent_ip, 'assign agent for record', **kwargs)
            _record.agent_ip = _agent_ip
            _record.agent_ip_bak = _agent_ip
            _record.status = '使用中'
            _record.hand_time = datetime.datetime.now()
            return DbCommit()


@api.route('/cancel')
class RecordCancel(Resource):
    @api.parameters(parameters.RecordCancelParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        取消刷新申请
        :param args:
        :return:
        """
        log.info("============== start cancel refresh agent ==============")
        _record_id = args['record_id']
        log.info("============== record_id is %s ==============" % _record_id)
        _record = ApplyRecord.query.filter_by(record_id = _record_id).first()
        if _record is None:
            _msg = '申请记录不存在 record_id:%s' % _record_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        else:
            addOperationRecord('CANCEL_REFRESH', _record_id, None, _record.agent_ip, 'cancel refresh agent', **kwargs)

            _record.status = '取消申请'
            return DbCommit()


@api.route('/add')
class RecordAdd(Resource):
    @api.parameters(parameters.RecordAddParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        添加申请
        :param args:
        :return:
        """
        log.info("============== start add record ==============")
        _type = args['type']
        _applicant_id = args['applicant_id']
        _applicant_name = args['applicant_name']
        _start_date = args['start_date']
        _expect_recover_date = args['expect_recover_date']
        _desc = args['desc']
        _plan_id = args['plan_id']
        _plan_name = args['plan_name']
        _isUseWeb = args['isUseWeb']
        _useWebDesc = args['useWebDesc']
        _useOldEnv = args['useOldEnv']
        log.info("============== type is %s ==============" % _type)
        log.info("============== applicant_id is %s ==============" % _applicant_id)
        log.info("============== applicant_name is %s ==============" % _applicant_name)
        log.info("============== start_date is %s ==============" % _start_date)
        log.info("============== expect_recover_date is %s ==============" % _expect_recover_date)
        log.info("============== desc is %s ==============" % _desc)
        log.info("============== plan_id is %s ==============" % _plan_id)
        log.info("============== plan_name is %s ==============" % _plan_name)
        log.info("============== isUseWeb is %s ==============" % _isUseWeb)
        log.info("============== useWebDesc is %s ==============" % _useWebDesc)
        log.info("============== useOldEnv is %s ==============" % _useOldEnv)

        addOperationRecord('ADD_APPLY', _plan_id, None, None, 'add apply record', **kwargs)

        if _isUseWeb is None or not _isUseWeb:
            _isUseWeb = '1'
        else:
            _isUseWeb = '0'

        if _useOldEnv is None or not _useOldEnv:
            _useOldEnv = '1'
        else:
            _useOldEnv = '0'

        if _plan_id is not None and ApplyRecord.query.filter_by(plan_id = _plan_id,
                                                                type = str(_type).upper()).first() is not None:
            _msg = '该记录%s 环境 %s已经申请过,不需要重新申请' % (_plan_name, _type)
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}

        _record = ApplyRecord(plan_id = _plan_id, plan_name = _plan_name, applicant_id = _applicant_id,
                              applicant_name = _applicant_name, start_date = _start_date,
                              expect_recover_date = _expect_recover_date, desc = _desc, status = '申请中',
                              type = str(_type).upper(), isUseWeb = _isUseWeb, useWebDesc = _useWebDesc,
                              useOldEnv = _useOldEnv)
        db.session.add(_record)
        return DbCommit()


@api.route('/update_agent')
class RecordUpdateAgent(Resource):
    @api.parameters(parameters.RecordCancelParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        申请刷新agent
        :param args:
        :return:
        """
        log.info("============== start apply refresh agent ==============")
        _record_id = args['record_id']
        log.info("============== record_id is %s ==============" % _record_id)
        _record = ApplyRecord.query.filter_by(record_id = _record_id).first()
        if _record is None:
            _msg = '申请记录不存在 record_id:%s' % _record_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        elif _record.agent_ip is None:
            _msg = '申请记录尚未分配agent record_id:%s' % _record_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        else:
            addOperationRecord('APPLY_REFRESH_AGENT', '%s|%s' % (_record_id, _record.agent_ip), _record.status, '申请刷新',
                               'apply refresh agent', **kwargs)
            _record.status = '申请刷新'
            return DbCommit()


@api.route('/updateAgentStatus')
class UpdateAgentStatus(Resource):
    @api.parameters(parameters.UpdateAgentStatusParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        修改agent状态,目前仅用于刷新agent完成后，agent调用
        :param args:
        :return:
        """
        log.info("============== start update agent status ==============")
        _agent_ip = args['agent_ip']
        log.info("============== agent_ip is %s ==============" % _agent_ip)
        _record = ApplyRecord.query.filter_by(agent_ip = _agent_ip).first()
        if _record is None:
            _msg = 'agent尚未进行分配 agent_ip:%s' % _agent_ip
            log.error('============== %s ==============' % _msg)
            return {'success': True, 'msg': _msg}
        else:
            addOperationRecord('UPDATE_AGENT_STATUS', _agent_ip, _record.status, '升级完成', 'update agent status',
                               **kwargs)
            _record.status = '升级完成'
            return DbCommit()


@api.route('/freeAgent')
class FreeAgent(Resource):
    @api.parameters(parameters.FreeAgentParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        项目归档后,释放agent
        :param args:
        :return:
        """
        log.info("============== start free agent ==============")
        plan_name = args['plan_name']
        apply_id = args['apply_id']
        log.info("============== plan_name is %s ==============" % plan_name)
        log.info("============== apply_id is %s ==============" % apply_id)
        _list = []

        addOperationRecord('FREE_AGENT', '%s|%s' % (apply_id, plan_name), None, None, 'free agent', **kwargs)

        if plan_name is None and apply_id is None:
            _msg = '参数plan_name apply_id 不能同时为空'
            log.error('============== %s ==============' % _msg)
            return {'success': True, 'msg': _msg}
        elif plan_name is not None and apply_id is None:
            _list = ApplyRecord.query.filter_by(plan_name = plan_name).all()
        elif plan_name is None and apply_id is not None:
            _list = ApplyRecord.query.filter_by(record_id = apply_id).all()
        elif plan_name is not None and apply_id is not None:
            _list = ApplyRecord.query.filter_by(record_id = apply_id, plan_name = plan_name).all()

        for _apply in _list:
            _apply.status = '项目结束,归档'
            _apply.agent_ip = None
        return DbCommit()


@api.route('/recoverAgent')
class RecoverAgent(Resource):
    @api.parameters(parameters.RecordCancelParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        回收agent
        :param args:
        :return:
        """
        log.info("============== start recover agent ==============")
        _record_id = args['record_id']
        log.info("============== record_id is %s ==============" % _record_id)

        addOperationRecord('RECOVER_AGENT', _record_id, None, None, 'recover agent', **kwargs)

        return recoverAgent(record_id = _record_id)


@api.route('/autoRecover')
class AutoRecover(Resource):
    @api.parameters(Parameters())
    @api.response(BaseSchema(many = False))
    def get(self, args, **kwargs):
        """
        一键回收
        :param args:
        :return:
        """
        log.info("============== start auto recover agent ==============")
        autoRecover()
        return {'success': True, 'msg': ''}


def autoRecover():
    """
    一键回收agent
    :return:
    """
    now = datetime.datetime.now()
    # 1、到期并且没有申请延期的
    for _apply in ApplyRecord.query.filter_by(isKeep = '1').filter(ApplyRecord.expect_recover_date <= now).all():
        recoverAgent(record_id = _apply.record_id)
    # 2、查询需要保留的超过7天的记录
    _time = now - datetime.timedelta(days = 7)
    for _apply in ApplyRecord.query.filter_by(isKeep = '0').filter(ApplyRecord.expect_recover_date <= _time).all():
        recoverAgent(record_id = _apply.record_id)


def recoverAgent(record_id = None):
    log.info("============== start recover agent record_id is %s ==============" % record_id)
    _record = ApplyRecord.query.filter_by(record_id = record_id).first()
    if _record is None:
        _msg = '申请记录不存在 record_id:%s' % record_id
        log.error('============== %s ==============' % _msg)
        return {'success': False, 'msg': _msg}

    if _record.agent_ip in BaseConfig.INTEGRATION_AGENT:
        _msg = '集成机器不回收 ip:%s' % _record.agent_ip
        log.error('============== %s ==============' % _msg)
        return {'success': False, 'msg': _msg}

    # 逻辑删除agent
    for _agent in Agent.query.filter_by(ip = _record.agent_ip).filter_by(is_del = '1').all():
        _agent.is_del = '0'
        addOperationRecord('AUTO_RECOVER', '%s|%s|%s' % (_agent.ip, _record.plan_name, _record.type), None, None,
                           '自动回收', login_user_id = 'beetle', login_user_name = 'beetle')

    _record.status = 'agent被回收'
    _record.agent_ip = None
    _record.recover_time = datetime.datetime.now()

    # 查询项目成员
    _url = "%s/project_plan/getPlanUserWithRole?token_id=1&plan_id=%s" % (BaseConfig.BEETLE_PLAN_URL, _record.plan_id)
    _to = 'lunan.xu@winit.com.cn,tongsan.chen@winit.com.cn,min.wan@winit.com.cn'
    try:
        _obj = recursive_json_loads(requests.get(url = _url).content)
        if len(_obj.data) != 0:
            for _me in _obj.data:
                _to = '%s,%s@winit.com.cn' % (_to, _me.account)
    except:
        pass
    # 邮件通知
    sendMail(_to = _to,
             _subject = 'ucloud机器回收',
             _body = '项目[%s]ucloud[%s]环境[%s]回收' % (_record.plan_name, _record.type, _record.agent_ip_bak))

    return DbCommit()


@api.route('/recoverAgentForIntegration')
class RecoverAgentForIntegration(Resource):
    @api.parameters(parameters.RecordCancelForIntegrationParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        集成计划重新规划后,回收agent
        :param args:
        :return:
        """
        log.info("============== start recover agent for integration==============")
        _integration_id = args['integration_id']

        addOperationRecord('RECOVER_AGENT_INTEGRATION', _integration_id, None, None, 'recover agent', **kwargs)

        _record = ApplyRecord.query.filter_by(plan_id = _integration_id).first()
        if _record is None:
            _msg = '申请记录不存在 integration_id:%s' % _integration_id
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}
        _record.status = 'agent被回收'
        _record.agent_ip = None
        _record.recover_time = datetime.datetime.now()
        return DbCommit()


@api.route('/removeRecord')
class RemoveRecord(Resource):
    @api.parameters(parameters.RemoveRecordParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        删除申请记录
        :param args:
        :return:
        """
        log.info("============== start remove apply record ==============")
        plan_id = args['plan_id']
        env_type = args['env_type']
        log.info("============== plan_id is %s ==============" % plan_id)
        log.info("============== env_type is %s ==============" % env_type)

        addOperationRecord('REMOVE_APPLY_RECORD', '%s|%s' % (plan_id, env_type), None, None, 'remove apply record',
                           **kwargs)

        _query = ApplyRecord.query.filter_by(plan_id = plan_id)

        if env_type is not None:
            _query = _query.filter_by(type = env_type)

        for _apply in _query.all():
            db.session.delete(_apply)

        return DbCommit()


@api.route('/addIntegrationApply')
class AddIntegrationApply(Resource):
    @api.parameters(parameters.RecordAddIntegrationApplyParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        集成计划规划完毕后,创建申请记录,并自动分配207机器
        :param args:
        :return:
        """
        log.info("============== start add integration apply record ==============")
        _integration_id = args['integration_id']
        _integration_name = args['integration_name']
        _start_date = args['start_date']
        _expect_recover_date = args['expect_recover_date']

        addOperationRecord('ADD_INTEGRATION_APPLY', _integration_id, None, None, 'add integration apply record',
                           **kwargs)

        # BaseConfig.INTEGRATION_AGENT 中有几个机器就生成几个集成计划的记录，前台访问跳转默认到207
        # 先删除集成计划申请记录，按照机器个数添加集成计划申请记录。
        for _apply in ApplyRecord.query.filter_by(plan_id = _integration_id).all():
            db.session.delete(_apply)

        for _ip in BaseConfig.INTEGRATION_AGENT:
            for _apply in ApplyRecord.query.filter_by(agent_ip = _ip).all():
                _apply.agent_ip = None
                _apply.status = 'agent被回收'
                _apply.recover_time = datetime.datetime.now()
            _record = ApplyRecord(plan_id = _integration_id, plan_name = _integration_name,
                                  applicant_id = kwargs['login_user_id'], applicant_name = kwargs['login_user_name'],
                                  start_date = _start_date, expect_recover_date = _expect_recover_date, status = '使用中',
                                  type = 'QA', agent_ip = _ip, agent_ip_bak = _ip, isIntegration = '0',
                                  hand_time = datetime.datetime.now())
            db.session.add(_record)

        # _agent_ip = None
        # # 寻找可用于集成的机器
        # for _ip in BaseConfig.INTEGRATION_AGENT:
        #     if _agent_ip is not None:
        #         break
        #     if Agent.query.filter_by(ip = _ip).first() is not None and len(
        #             ApplyRecord.query.filter_by(agent_ip = _ip).all()) == 0:
        #         _agent_ip = _ip
        #
        # if _agent_ip is None:
        #     _agent_ip = BaseConfig.INTEGRATION_AGENT[0]
        #     for _apply in ApplyRecord.query.filter_by(agent_ip = _agent_ip).all():
        #         _apply.agent_ip = None
        #         _apply.status = 'agent被回收'
        #
        # _info = ApplyRecord.query.filter_by(plan_id = _integration_id).first()
        # if _info is None:
        #     _record = ApplyRecord(plan_id = _integration_id, plan_name = _integration_name,
        #                           applicant_id = kwargs['login_user_id'], applicant_name = kwargs['login_user_name'],
        #                           start_date = _start_date, expect_recover_date = _expect_recover_date, status = '使用中',
        #                           type = 'QA', agent_ip = _agent_ip, isIntegration = '0')
        #     db.session.add(_record)
        # else:
        #     _info.status = '使用中'
        #     _info.agent_status = None
        #     _info.agent_ip = _agent_ip

        return DbCommit()


@api.route('/keep')
class Keep(Resource):
    @api.parameters(parameters.KeepParameters())
    @api.response(BaseSchema(many = False))
    @checkToken
    def get(self, args, **kwargs):
        """
        申请保留机器，默认保留7天
        :param args:
        :return:
        """
        log.info("============== start apply keep agent ==============")
        _plan_id = args['plan_id']
        _agent_ip = args['agent_ip']
        _apply_id = args['apply_id']

        if _plan_id is None and _agent_ip is None and _apply_id is None:
            _msg = '参数不能全部为空'
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}

        addOperationRecord('KEEP_APPLY_AGENT', '%s|%s|%s' % (_plan_id, _agent_ip, _apply_id), None, None, 'keep agent',
                           **kwargs)

        _query = ApplyRecord.query

        if _plan_id is not None:
            _query = _query.filter_by(plan_id = _plan_id)

        if _agent_ip is not None:
            _query = _query.filter_by(agent_ip = _agent_ip)
        if _apply_id is not None:
            _query = _query.filter_by(record_id = _apply_id)

        _list = _query.all()
        if len(_list) == 0:
            _msg = '根据参数查询记录为空,plan_id=%s,agent_ip=%s,apply_id=%s' % (_plan_id, _agent_ip, _apply_id)
            log.error('============== %s ==============' % _msg)
            return {'success': False, 'msg': _msg}

        for _apply in _list:
            _apply.isKeep = '0'

        return DbCommit()


@api.route('/updateApplyEndTime')
class UpdateApplyEndTime(Resource):
    @api.parameters(parameters.UpdateApplyEndTimeParameters())
    @api.response(BaseSchema(many = False))
    def get(self, args, **kwargs):
        """
        更新plan所有申请的结束时间
        :param args:
        :return:
        """
        log.info("============== start update apply end time ==============")
        _plan_id = args['plan_id']
        to_date = args['to_date']

        # addOperationRecord('UPDATE_APPLY_RECOVER_DATE', '%s' % _plan_id, None, None, 'update apply recover date',
        #                    **kwargs)

        _list = ApplyRecord.query.filter_by(plan_id = _plan_id).all()
        if len(_list) == 0:
            _msg = 'plan_id=%s没有申请虚拟机' % _plan_id
            log.error('============== %s ==============' % _msg)
            return {'success': True, 'msg': _msg}

        for _apply in _list:
            _apply.expect_recover_date = to_date

        return DbCommit()


@api.route('/planNotify')
class planNotify(Resource):
    @api.parameters(parameters.PlanNotifyParameters())
    @api.response(BaseSchema(many = False))
    def get(self, args, **kwargs):
        """
        邮件通知项目到期，ucloud回收
        :param args:
        :return:
        """
        _day = args['day']
        log.info('app.modules.apply.resources.planNotify start . day is %s' % _day)
        _list = getEndApplyList(_day = _day)
        notifyPlanPm(_list)
        return {'success': True, 'msg': ""}


def getEndApplyList(_day = 7):
    """
    查询结束时间还剩7天内的申请列表
    :return:
    """
    now = datetime.datetime.now()
    _time = now + datetime.timedelta(days = _day)
    _list = ApplyRecord.query.filter(ApplyRecord.expect_recover_date <= _time).filter(
        ApplyRecord.status == '使用中').all()
    log.info('app.modules.apply.resources.getEndApplyList list size is %s' % len(_list))
    return _list


def notifyPlanPm(_records):
    """
    通知这些申请对应的负责人
    :return:
    """
    for _r in _records:
        # 查询项目成员
        _url = "%s/project_plan/getPlanUserWithRole?token_id=1&plan_id=%s" % (BaseConfig.BEETLE_PLAN_URL, _r.plan_id)
        _to = 'lunan.xu@winit.com.cn,tongsan.chen@winit.com.cn,min.wan@winit.com.cn'
        try:
            _obj = recursive_json_loads(requests.get(url = _url).content)
            if len(_obj.data) != 0:
                for _me in _obj.data:
                    _to = '%s,%s@winit.com.cn' % (_to, _me.account)
        except:
            log.error(traceback.format_exc())
        # 邮件通知
        sendMail(_to = _to,
                 _subject = '项目[%s]到期通知.vmip:[%s]' % (_r.plan_name, _r.agent_ip_bak),
                 _body = '项目[%s]将在[%s]到期,项目到期后ucloud自动回收.如果需要继续使用,请前往禅道修改项目时间!' % (
                     _r.plan_name, _r.expect_recover_date))
